package com.rengwuxian.coursecoroutines._4_flow

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.fold
import kotlinx.coroutines.flow.reduce
import kotlinx.coroutines.flow.runningFold
import kotlinx.coroutines.flow.runningReduce
import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.EmptyCoroutineContext

/**
 * reduce() / fold() 和 running-()操作符
 * reduce() / runningReduce() / fold() / runningFold() / scan()
 *
 * 1.reduce() : 在数学领域等差数列的求和，就是reduce的意思。只返回最后一次计算结果。
 *   第一个数据就是acc的初始值，i是第二个元素的值，开始计算。
 *   list中的reduce(),积累的一种处理方式（最终结果类型是不变的），比如求和。list.reduce { acc, i -> acc + i }.let { println("List reduced to $it") }
 *       reduce()是Iterable的函数，所有有枚举特性的数据类型，都可以使用。
 *   Flow中的reduce(),作用和list中的reduce()是一样的，用所有的元素来计算一个最终结果，只不过它是Flow的版本。
 *       flow1.reduce { accumulator, value -> accumulator + value }
 *       它的返回值就不是一个Flow了，而是直接启动Flow的收集过程，然后在收集结束之后，返回最终的计算结果。
 *       返回结果的类型，就是Flow的元素类型。
 *   像这种启动了Flow的收集过程的操作符，被称为terminal operator。
 * 2.runningReduce() :把所有数据，像驴打滚似的计算一遍，返回的结果依然是Flow。
 *   list中的runningReduce()的函数，不是用来计算最终结果的，会生成一个新的List,里面的元素都是大括号中提供的算法每次计算出来的值。
 *       list.runningReduce { acc, i -> acc + i }.let { println("New List: $it") }
 *   Flow的runningReduce()的函数，也不是用来计算最终结果的，不会直接启动收集函数，它是一个Flow的版本，会生成一个新的Flow，里面的元素都是大括号中提供的算法每次计算出来的值。
 * 3.fold(）： 折叠，返回最终计算结果。
 *   list中的fold()函数，和reduce()功能差不多，可以提示一个初始值，作为第一轮计算的两个输入值之一，从第1个元素开始计算。
 *   初始值类型可以和元素值不一样，输出结果类型和初始值类型一致。--》也就是说输出值的类型不受限制的。
 *   Flow中的fold()和list一样，是Flow版本的而已。直接启动collect收集过程，返回最终结果。
 *
 * 4.runningFold(): 返回每一轮计算过程中的结果。
 *   list中的runningFold()函数，返回List，里面元素的值都是通过每一轮计算得出的。
 *   存在初始值的，且初始值本身会作为计算的第一个元素添加进去。
 *   Flow中的rūnningFold()函数，就是Flow版本的list。
 *
 * 5.scan() : 扫描，是runningFold()的别名函数，一摸一样。
 *
 * 输出结果：
 * List reduced to 15
 * New List: [1, 3, 6, 10, 15]
 * List folded to 25
 * List folded to string: ha - 1 - 2 - 3 - 4 - 5
 * New String List: [ha, ha - 1, ha - 1 - 2, ha - 1 - 2 - 3, ha - 1 - 2 - 3 - 4, ha - 1 - 2 - 3 - 4 - 5]
 * Sum is 15
 * runningReduce: 1
 * runningReduce: 3
 * runningReduce: 6
 * runningReduce: 10
 * runningReduce: 15
 * Flow folded to string: ha - 1 - 2 - 3 - 4 - 5
 * Flow.runningFold(): ha
 * Flow.runningFold(): ha - 1
 * Flow.runningFold(): ha - 1 - 2
 * Flow.runningFold(): ha - 1 - 2 - 3
 * Flow.runningFold(): ha - 1 - 2 - 3 - 4
 * Flow.runningFold(): ha - 1 - 2 - 3 - 4 - 5
 * Flow.scan(): ha
 * Flow.scan(): ha - 1
 * Flow.scan(): ha - 1 - 2
 * Flow.scan(): ha - 1 - 2 - 3
 * Flow.scan(): ha - 1 - 2 - 3 - 4
 * Flow.scan(): ha - 1 - 2 - 3 - 4 - 5
 */
fun main() = runBlocking<Unit> {
  val scope = CoroutineScope(EmptyCoroutineContext)
  val flow1 = flowOf(1, 2, 3, 4, 5)
  val list = listOf(1, 2, 3, 4, 5)
  list.reduce { acc, i -> acc + i }.let { println("List reduced to $it") }
  list.runningReduce { acc, i -> acc + i }.let { println("New List: $it") }
  list.fold(10) { acc, i -> acc + i }.let { println("List folded to $it") }
  list.fold("ha") { acc, i -> "$acc - $i" }.let { println("List folded to string: $it") }
  list.runningFold("ha") { acc, i -> "$acc - $i" }.let { println("New String List: $it") }
  scope.launch {
    val sum = flow1.reduce { accumulator, value -> accumulator + value }
    println("Sum is $sum")
    flow1.runningReduce { accumulator, value -> accumulator + value }
      .collect { println("runningReduce: $it") }
    flow1.fold("ha") { acc, i -> "$acc - $i" }.let { println("Flow folded to string: $it") }
    flow1.runningFold("ha") { acc, i -> "$acc - $i" }
      .collect { println("Flow.runningFold(): $it") }
    flow1.scan("ha") { acc, i -> "$acc - $i" }
      .collect { println("Flow.scan(): $it") }
  }
  delay(10000)
}